from typing import Optional
from datetime import datetime, timedelta

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

from passlib.context import CryptContext

from jose import JWTError, jwt

SECRET_KEY = 'e7'  # secret_key
ALGORITHM = 'HS256'  # 算法 algorithm
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 访问令牌过期分钟


class JWTUtils(object):
    """
        Model:
            处理JWT事务，可以被依赖使用

        Param:
            - model: 使用哪个表作为用户表
            - name: 使用哪个字段作为用户名字段
            - possword: 使用哪个字段作为密码字段
            - env: odoo env
            - crypt_context: CryptContext对象
            - oauth2_schema: OAuth2PasswordBearer对象

        Function:
            - create_crypt_context: 创建crypt_context对象
            - create_oauth2_schema: 创建oauth2对象
            - verify_password: 对密码进行校验
            - encrypt_password: 对密码进行加密
            - authenticate_user: 用户校验
        """

    def __init__(self, model, name='name', possword='possword', env=None, crypt_context=None, oauth2_schema=None):
        self.model = model
        self.name = name
        self.possword = possword
        self.env = env
        self.crypt_context = crypt_context
        self.oauth2_schema = oauth2_schema

    def create_crypt_context(self, schemes='bcrypt'):
        """
        创建crypt_context对象

        passlib 密码散列和验证库
        CryptContext 是用来处理密码散列和验证的多种算法的安全策略

        - schemes: 指定CryptContext应该使用哪些散列算法，
             - bcrypt: 是一种基于Blowflsh加密算法的适用于密码存储的自适应散列函数。它可以通过增加工作因子来提高计算复杂性，从而更有效地抵御暴力破解攻击
             - pbkdf2_sha512: odoo使用的是这种加密算法

        - deprecated: 这个参数用来处理旧算法的迁移和淘汰问题。设置为 'auto' 意味着 CryptContext 会自动将使用已被标记为不推荐的散列算法的密码，在下次验证时升级到推荐的散列算法。这是一种安全措施，确保密码的存储始终保持更新，符合当前的安全标准。
        """
        self.crypt_context = CryptContext(schemes=[schemes], deprecated='auto')

    def create_oauth2_schema(self, url):
        """
        创建OAuth2PasswordBearer对象
        :param url :tokenUrl

        OAuth2PasswordBearer 用于实现 OAuth2 密码流（Password Flow）授权

        在 OAuth2 中，"bearer" 令牌是一种访问令牌，允许持有者访问受保护的资源，而无需再次验证身份。
        当你在代码中创建一个 OAuth2PasswordBearer 实例时，你实际上是在定义一个安全方案，用于自动从客户端请求的 Authorization 头部中提取令牌。这个令牌之后将用于进一步的身份验证和授权操作。

        - tokenUrl: 这个参数是获取令牌的 URL。这意味着当客户端需要获取一个令牌以验证用户的身份时，它需要向这个 URL 发送一个请求。通常这个请求需要包含用户名和密码，如果验证成功，服务器会返回一个令牌。
        """
        self.oauth2_schema = OAuth2PasswordBearer(tokenUrl=url)

    def verify_password(self, plain_password: str, hashed_password: str):
        """
        对密码进行校验

        :param plain_password: 明文密码
        :param hashed_password: 哈希加密后的密码

        @return True/False
        """
        return self.crypt_context.verify(plain_password, hashed_password)

    def encrypt_password(self, plain_password: str):
        """
        对密码进行加密

        :param plain_password: 明文密码

        @return hashed_password: 哈希加密后的密码
        """
        return self.crypt_context.hash(plain_password)

    def authenticate_user(self, username: str, password: str):
        """
        用户校验
        :param username: 用户名
        :param password: 明文密码
        """
        # 1、根据用户名获取用户
        user = self.env[self.model].sudo().search([('username', '=', username), ('active', '=', True)])
        if not user:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                                detail='US0001',
                                headers={'WWW-Authenticate': 'Bearer'})
        # 2、根据用户校验密码
        if not self.verify_password(plain_password=password, hashed_password=user.password):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                                detail='US0002',
                                headers={'WWW-Authenticate': 'Bearer'})
        return user

    def created_access_token(self, userID, expires_delta: Optional[timedelta] = None):
        """
        获取token

        :param userID: 用户ID
        :param expires_delta: 过期时间
        """

        if expires_delta:
            expires = datetime.utcnow() + expires_delta
        else:
            expires = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)

        encode_jwt = jwt.encode(claims={'userID': userID,
                                        'exp': expires}, key=SECRET_KEY, algorithm=ALGORITHM)

        return encode_jwt


def jwt_get_current_user(token):
    """获取当前用户"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail='Incorrect username or password',
        headers={'WWW-Authenticate': 'Bearer'}
    )

    try:
        payload = jwt.decode(token=token, key=SECRET_KEY, algorithms=[ALGORITHM])
        userID = payload.get('userID')
        if userID is None:
            return {'code': 401}
        return {'code': 200,
                'userID': userID}
    except:
        return {'code': 403}
#
# async def jwt_get_current_active_user(current_user: User = Depends(jwt_get_current_user)):
#     """获取当前活跃的用户"""
#     if current_user.disabled:
#         raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Inactive user')
#     return current_user
#
#
# @app06.get('/jwt/user/me')
# async def jwt_read_user_me(current_user: User = Depends(jwt_get_current_active_user)):
#     return current_user
